using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.IO;
using System.Xml.Serialization;
using System.Diagnostics;
using Waymex.Diagnostics;

namespace Waymex.Buffers
{
    /// <summary>
    /// The ByteFifo class provides a thread safe cyclic byte buffer.
    /// </summary>
    internal class ByteFifo
    {
        private const string ERR_MSG_NO_DATA = "An attempt was made to read the buffer when it was empty.";

        private int m_bufferInPtr = 0;
        private int m_bufferOutPtr = 0;
        private int m_size;
        private string m_recentDataLastAdded = "";
        private byte[] m_buffer;

        //use private object to control a lock as apposed to the 'this' reference so that if
        //anyone chooses to lock this class within external code, deadlocks wont occur
        private object syncLock = new object();

        //private ReaderWriterLock objReadWriteLock = new ReaderWriterLock();

        /// <summary>
        /// Construtor for the buffer. Accepts a single parameter 
        /// that initialises the buffer to the number of bytes specified.
        /// </summary>
        /// <param name="BufferSize">Size of buffer in bytes.</param>
        internal ByteFifo(int BufferSize)
        {
            m_size = BufferSize;
            m_buffer = new byte[m_size];

        }
        /// <summary>
        /// Returns the number of bytes in the buffer.
        /// </summary>
        internal int Count
        {
            get
            {
                lock (syncLock)
                {
                    int count;

                    if (m_bufferInPtr - m_bufferOutPtr < 0)
                        count = m_bufferOutPtr - m_bufferInPtr;
                    else
                        count = m_bufferInPtr - m_bufferOutPtr;

                    //Console.Write("Count:" + count.ToString());

                    return count;
                }
            }
        }
        /// <summary>
        /// Returns the data that was last added to the buffer. This property does not access
        /// the buffer directly it simply reads the incomming cache. The data in the buffer is not affected.
        /// </summary>
        internal string DataLastAdded
        {
            get
            {
                lock (syncLock)
                {
                    return m_recentDataLastAdded;
                }
            }
        }
        /// <summary>
        /// Returns the buffer out pointer.
        /// </summary>
        internal int BufferOutPointer
        {
            get
            {
                lock (syncLock)
                {
                    return m_bufferOutPtr;
                }
            }
        }
        /// <summary>
        /// Returns the buffer in pointer.
        /// </summary>
        internal int BufferInPointer
        {
            get
            {
                lock (syncLock)
                {
                    return m_bufferInPtr;
                }
            }
        }
        /// <summary>
        /// Returns the size of the buffer.
        /// </summary>
        internal int Size
        {
            get
            {
                return m_size;
            }
        }
        /// <summary>
        /// This method adds bytes to the buffer.
        /// </summary>
        /// <param name="Bytes"></param>
        internal void Add(byte[] Bytes)
        {
            int byteCount;

            lock (syncLock)
            {
                //first determine the length of the data
                byteCount = Bytes.Length;

                try
                {
                    m_recentDataLastAdded = ByteToHex(Bytes);

                    //see if the buffer can accept the data without wrapping
                    if (m_bufferInPtr + byteCount <= Size)
                    {
                        System.Array.Copy(Bytes, 0, m_buffer, m_bufferInPtr, byteCount);
                        m_bufferInPtr = m_bufferInPtr + byteCount;
                    }
                    else
                    {
                        Console.WriteLine("Add - Rolling Buffer");

                        //increment/roll the byte counter
                        for (int count = 0; count < byteCount; count++)
                        {
                            m_buffer[m_bufferInPtr] = Bytes[count];

                            m_bufferInPtr++;

                            //check to see if that was the last buffer byte
                            if (m_bufferInPtr == Size)
                                m_bufferInPtr = 0;

                        }
                    }
                }
                finally
                {
                    //				if (objReadWriteLock.IsReaderLockHeld)
                    //					objReadWriteLock.ReleaseReaderLock();
                    //				if (objReadWriteLock.IsWriterLockHeld)
                    //					objReadWriteLock.ReleaseWriterLock();
                }

                //Console.WriteLine("BufferIn : " + m_bufferInPtr.ToString());
                //Console.WriteLine("BufferOut: " + m_bufferOutPtr.ToString());
            }

        }
        /// <summary>
        /// Clears the contents of the buffer.
        /// </summary>
        internal void Clear()
        {
            lock (syncLock)
            {
                //m_bufferOutPtr = m_bufferInPtr;
                m_bufferOutPtr = 0;
                m_bufferInPtr = 0;
            }
        }
        /// <summary>
        /// Reads the next byte in the buffer. This method does not remove the byte.
        /// Returns null if no bytes in the buffer.
        /// </summary>
        /// <returns>The next available byte, throws an exception if the buffer is empty.</returns>
        internal byte ReadByte()
        {
            lock (syncLock)
            {
                //Console.WriteLine("ReadByte - InPointer:" + m_bufferInPtr.ToString() + " Bytes in Buffer:" + (m_bufferInPtr - m_bufferOutPtr).ToString());

                //check to see if there is a byte to read
                if (m_bufferInPtr - m_bufferOutPtr != 0)
                {
                    //return the next byte no need to check for roll etc as we are only reading
                    return m_buffer[m_bufferOutPtr];
                }
                else
                {
                    throw new BufferException(ERR_MSG_NO_DATA);
                }
            }
        }
        /// <summary>
        /// Reads the next byte in the buffer. This method removes the byte.
        /// Returns null if no bytes in the buffer.
        /// </summary>
        /// <returns>The next available byte, throws an exception if the buffer is empty.</returns>
        internal byte RemoveByte()
        {
            lock (syncLock)
            {

                byte returnData;

                //Console.WriteLine("RemoveByte - InPointer:" + m_bufferInPtr.ToString() + " Bytes in Buffer:" + (m_bufferInPtr - m_bufferOutPtr).ToString());
                //check to see if there is a byte to read
                if (m_bufferInPtr - m_bufferOutPtr != 0)
                {
                    returnData = m_buffer[m_bufferOutPtr];
                    m_bufferOutPtr++;

                    //increment or roll the buffer out pointer
                    if (m_bufferOutPtr == Size)
                        m_bufferOutPtr = 0;

                    return returnData;
                }
                else
                {
                    throw new BufferException(ERR_MSG_NO_DATA);
                }
            }
        }
        /// <summary>
        /// This function converts an array of bytes to a string, of hex digits representing those bytes.
        /// No Synchronisation needed as it is private and can only becalled from a thread safe method.
        /// </summary>
        /// <param name="ByteData"></param>
        /// <returns></returns>
        private string ByteToHex(byte[] ByteData)
        {
            string sTemp = "";
            string sMessage = "";

            try
            {
                if (ByteData != null)
                {
                    for (int f = 0; f <= ByteData.GetUpperBound(0); f++)
                    {
                        sTemp = ByteData[f].ToString("X"); //hex returns a string representing the passed number

                        if (sTemp.Length < 2)
                        {
                            sTemp = string.Concat("0", sTemp);
                        }

                        sMessage = string.Concat(sMessage, " ", sTemp);
                    }

                    return sMessage.Trim();
                }
                else
                {
                    return "";
                }
            }
            catch
            {
                throw;
            }
        }
    }
}
